/**
 * videoCall 1.2.0
 * powerBy 王小向
 */
import EventEmitter from 'events'

class VideoCall extends EventEmitter {
  constructor({
    socket,
    sockets,
    timeout,
    remoteVideoEle,
    localVideoEle,
    userId,
    eventName
  }) {
    super()
    this.socket = socket
    this.sockets = sockets
    this.timeout = timeout
    this.remoteVideoEle = remoteVideoEle
    this.localVideoEle = localVideoEle
    this.userId = userId
    this.eventName = eventName || 'video_call'
    this.init()
  }
  init() {
    this.pc = null
    this.localMediaStream = null
    this.callTimerId = null
    this.status = 'free' // free-空闲，calling-呼叫中,called-被呼叫中，通话连接-connecting,busy-通话中
    this.messageMap = this.getMessageMap()
    this.deviceType = this.getDeviceType()
    this.otherId = null
    this.addTimes = 0
    this.callType = null
    this.mountSocketEvent()
    this.mountUniEvent()
  }

  send({ to, type, data, callType }) {
    const sendData = {
      type,
      to,
      callType,
      data,
      timeStamp: Date.now()
    }
    console.log('发送数据：', sendData)
    this.socket.emit(this.eventName, sendData)
  }

  sendToOther(type, data) {
    if (this.otherId == null) {
      throw new Error(this.t('noOther'))
    }
    if (this.callType == null) {
      throw new Error('通话类型未指定')
    }
    this.send({ to: this.otherId, callType: this.callType, type, data })
  }

  getDeviceType() {
    const uA = navigator.userAgent.toLowerCase()
    const ipad = uA.match(/ipad/i) === 'ipad'
    const iphone = uA.match(/iphone os/i) === 'iphone os'
    const android = uA.match(/android/i) === 'android'
    const windowsce = uA.match(/windows ce/i) === 'windows ce'
    const windowsmd = uA.match(/windows mobile/i) === 'windows mobile'
    if (ipad || iphone || android || windowsce || windowsmd) {
      return 'phone'
    } else {
      return 'pc'
    }
  }

  getMessageMap(messageMap) {
    if (messageMap && messageMap.toString() === '[object Object]') {
      return messageMap
    } else {
      return {
        hangUp: '对方已经挂断，连接失败',
        rejectCall: '对方拒绝通话',
        busy: '对方正在通话中',
        timeout: '对方未接听',
        noVideo: '未发现摄像头',
        noAudio: '未发现麦克风',
        opendDeviceError: '打开摄像头或者麦克风失败',
        notSupportWebRTC: '浏览器不支持WebRTC！',
        connectionSuccess: '连接成功',
        connectionError: '连接失败',
        noOther: '未获取到对方的ID',
        nonsupport: '对方设备不支持视频通话',
        meNonsupport: '此设备不支持视频通话'
      }
    }
  }

  t(code) {
    return this.messageMap[code]
  }

  statusChange(status) {
    this.status = status
    this.emit('status', { status, callType: this.callType })
  }

  readyPeerConnection() {
    const PeerConnection =
      window.PeerConnection ||
      window.webkitPeerConnection ||
      window.webkitRTCPeerConnection ||
      window.mozRTCPeerConnection
    if (!PeerConnection) {
      throw new Error(this.t('notSupportWebRTC'))
    }
    const pc = new PeerConnection()
    this.pc = pc
    pc.ontrack = (e) => {
      console.log('pc.ontrack---', e.streams)
      if (e && e.streams) {
        this.remoteVideoEle.srcObject = e.streams[0]
        // this.statusChange('busy')
        // const msg = this.t('connectionSuccess')
      }
    }
    pc.oniceconnectionstatechange = () => {
      if (
        pc.iceConnectionState === 'failed' ||
        pc.iceConnectionState === 'disconnected' ||
        pc.iceConnectionState === 'closed'
      ) {
        console.log('连接失败，给对方发送关闭连接信息')
        this.emit('message', { type: 'fail', text: this.t('connectionError') })
        this.closeConnection()
      } else if (pc.iceConnectionState === 'connected') {
        console.log('连接成功 statusChange --busy')
        this.statusChange('busy')
        const msg = this.t('connectionSuccess')
        this.emit('message', { type: 'success', text: msg })
      }
    }

    pc.onicecandidate = (e) => {
      console.log('pc.onicecandidate----', e)
      if (e.candidate) {
        console.log('sendToOther ice_candidate')
        this.sendToOther('ice_candidate', e.candidate)
      }
    }
  }

  mountSocketEvent() {
    const actionMap = {
      offer: this.hanldeRecieveOffer,
      answer: this.handleRecieveAnswer,
      ice_candidate: this.handleIceCandidate,
      request_call: this.handleRequestCall,
      allow_call: this.handleAllowCall,
      reject_call: this.handleRejected,
      close_connection: this.handleCloseConnection
    }
    this.sockets.subscribe('video_call', async(res) => {
      const {
        type,
        from
      } = res
      console.log('socket接收到的videocall事件类型：', type)
      if (!type) return
      const actionFunc = actionMap[type]
      if (typeof actionFunc !== 'function') return
      if (this.otherId === from || type === 'request_call') {
        console.log('执行函数 ', actionFunc.name)
        await actionFunc.call(this, res)
      }
    })
  }

  mountUniEvent() {
    console.log('mountUniEvent')
    this.on('callRecieve', (id) => {
      this.clearCallTimer()
      if (this.status === 'called') {
        this.agreeCall(id)
      }
    })
    this.on('callReject', (id) => {
      this.clearCallTimer()
      if (this.status === 'called' || this.status === 'calling') {
        this.disAgreeCall(id)
      }
      if (this.status === 'connecting') {
        this.hangUp()
      }
    })
  }

  async handleRequestCall(res) {
    console.log('收到视频通话请求，user=', res)
    if (this.status !== 'free') {
      this.send(res.from, 'reject_call', this.t('busy'))
    } else {
      this.otherId = res.from
      this.callType = res.callType
      this.statusChange('called')
      this.emit('callConfirm', res)
    }
  }

  async hanldeRecieveOffer({ data }) {
    console.log('收到对方的offer')
    const offerSdp = new RTCSessionDescription(data)
    await this.pc.setRemoteDescription(offerSdp)
    console.log('使用offer设置 RemoteDescription')
    const answer = await this.pc.createAnswer()
    await this.pc.setLocalDescription(answer)
    console.log('创建answer 设置 LocalDescription')
    this.sendToOther('answer', answer)
    console.log('成功发送answer')
    this.sendToOther('msg', '对方已经收到你的Offer,并给你发送了answer')
  }

  async handleRecieveAnswer({ data }) {
    console.log('收到对方的answer')
    const answerSDP = new RTCSessionDescription(data)
    await this.pc.setRemoteDescription(answerSDP)
    console.log('使用answer 设置 LocalDescription')
    this.sendToOther('msg', '对方已经收到你的answer，正在连接中')
  }

  async handleIceCandidate({ data }) {
    console.log('收到对方的 IceCandidate，并执行addIceCandidate')
    console.log('handleIceCandidate-----', data)
    console.log('remtoeDes', this.pc.remoteDescription)
    console.log('lcoalDes', this.pc.localDescription)
    await this.pc.addIceCandidate(data)
    // if (this.addTimes <= 10) {
    //   try {
    //     this.addTimes++
    //     await this.pc.addIceCandidate(data)
    //   } catch (e) {
    //     console.log('addIceCandidate 出现错误，重新添加')
    //     setTimeout(() => {
    //       this.handleIceCandidate(data)
    //     }, 200)
    //   }
    // }
  }

  async handleRejected() {
    this.emit('message', { type: 'fail', text: this.t('rejectCall') })
    this.clearCallTimer()
    this.closeConnection()
  }

  reset() {
    console.log('reset 视频通话')
    this.otherId = null
    if (this.pc) {
      this.pc.ontrack = null
      this.pc.onicecandidate = null
      this.pc.oniceconnectionstatechange = null
      // this.pc.removeTrack()
      this.pc.close()
      this.pc = null
      this.addTimes = 0
    }
    this.callType = null
    this.closeVideo()
    this.statusChange('free')
  }

  closeConnection(msg) {
    if (msg) {
      this.emit('message', { type: 'success', text: msg })
    }
    this.reset()
  }

  handleCloseConnection(res) {
    const msg = res?.data
    this.closeConnection(msg)
  }

  async handleAllowCall() {
    console.log('对方已经同意通话请求')
    if (!this.pc) {
      this.sendToOther('close_connection', this.t('oppositeHangup'))
    } else {
      this.clearCallTimer()
      await this.sendOffer()
      this.statusChange('connecting')
    }
  }

  async ready() {
    console.log('ready device ---calltype', this.callType)
    this.readyPeerConnection()
    this.readyMediaEle()
    await this.oppenLocalDevice()
    console.log('ready over----')
  }

  async agreeCall(otherId) {
    console.log('同意对方的通话请求')
    this.clearCallTimer()
    try {
      this.otherId = otherId
      this.statusChange('connecting')
      await this.ready()
      this.sendToOther('allow_call')
    } catch (e) {
      console.error(e)
      this.emit('error', e)
      this.sendToOther('reject_call', this.t('nonsupport'))
      this.emit('message', { type: 'fail', text: this.t('meNonsupport') })
      this.reset()
    }
  }

  disAgreeCall(otherId) {
    this.clearCallTimer()
    this.send({
      to: otherId,
      type: 'reject_call',
      data: this.t('rejectCall')
    })
    this.statusChange('free')
    this.reset()
  }

  async sendOffer() {
    const myOffer = await this.pc.createOffer()
    await this.pc.setLocalDescription(myOffer)
    console.log('生成offer，并设置LocalDescription，发送offer')
    this.sendToOther('offer', myOffer)
  }

  async call(otherId, callType, data) {
    console.log('发起通话请求 in call function', otherId, data)
    try {
      this.otherId = otherId
      this.callType = callType
      this.statusChange('calling')
      await this.ready()
      console.log('发送通话请求-----')
      this.sendToOther('request_call', data)
      this.callTimerId = setTimeout(() => {
        console.log('超时被执行，连接关闭，发送给对方关闭连接')
        if (this.status === 'calling') {
          this.sendToOther('close_connection')
          this.closeConnection(this.t('timeout'))
        }
      }, this.timeout)
      return true
    } catch (e) {
      this.emit('error', e)
      this.reset()
      return false
    }
  }

  // 检查硬件音频、视频输入硬件
  async checkDevices() {
    const devices = await navigator.mediaDevices.enumerateDevices()
    let [video, audio] = [false, false]
    devices.forEach((device) => {
      if (device.kind === 'audioinput') {
        audio = true
      }
      if (device.kind === 'videoinput') {
        video = true
      }
    })
    if (!video) {
      throw new Error(this.t('noVideo'))
    }
    if (!audio) {
      throw new Error(this.t('noAudio'))
    }
  }

  async oppenLocalVideoDevice() {
    const pcVideo = true
    const phoneVideo = {
      width: {
        min: 360,
        ideal: 720,
        max: 1080
      }
    }
    const constraints = {
      audio: true,
      video: this.deviceType === 'pc' ? pcVideo : phoneVideo
    }
    // await this.checkDevices();
    const stream = await navigator.mediaDevices.getUserMedia(
      constraints
    )
    console.log('this.localVideoEle---', this.localVideoEle)
    this.localVideoEle.srcObject = stream
    // throw new Error('opendDeviceError');
    this.localMediaStream = stream
    stream.getTracks().forEach((track) => {
      this.pc.addTrack(track, stream)
    })
  }

  async oppenLocalAudioDevice() {
    const stream = await navigator.mediaDevices.getUserMedia(
      { audio: true }
    )
    this.localMediaStream = stream
    stream.getTracks().forEach((track) => {
      this.pc.addTrack(track, stream)
    })
  }

  async oppenLocalDevice() {
    if (this.callType === 'video') {
      await this.oppenLocalVideoDevice()
    }
    if (this.callType === 'audio') {
      await this.oppenLocalAudioDevice()
    }
  }

  readyMediaEle() {
    if (this.callType === 'video') {
      this.localVideoEle.onloadeddata = () => {
        this.localVideoEle.play()
      }
    }
    this.remoteVideoEle.onloadeddata = () => {
      this.remoteVideoEle.play()
    }
  }

  hangUp(msg) {
    console.log('点击了主动挂断')
    this.clearCallTimer()
    this.sendToOther('close_connection', msg)
    this.closeConnection()
  }

  closeVideo() {
    if (this.localMediaStream) {
      this.localMediaStream.getTracks().forEach(track => {
        track.stop()
      })
    }
    this.localMediaStream = null
    this.localVideoEle.srcObject = null
    this.remoteVideoEle.srcObject = null
  }

  clearCallTimer() {
    console.log('清除 callTimerId')
    clearTimeout(this.callTimerId)
    this.callTimerId = null
  }
}

export default VideoCall
